#' Split intraday sessions to time intervals
#'
#' @param period size of interval in seconds
#' @param session_def a list of session definition, each a vector of 6
#'
#' @return a vector of ITime
#' @export
#'
itime_interval <- function(period, session_def = list(c(9,30,0,11,30,0),
                                                      c(13,0,0,15,0,0)))
{
  by <- as.integer(period)
  intv <- list()
  for (i in seq_along(session_def))
  {
    s <- as.integer(session_def[[i]])
    t1 <- s[1L] * 3600L + s[2L] * 60L + s[3L]
    t2 <- s[4L] * 3600L + s[5L] * 60L + s[6L]
    tt <- t2 - t1
    if (tt %% by)
    {
      msg <- sprintf("Can't split %d seconds by %d in session %d", tt, by, i)
      stop(msg, call. = FALSE)
    }
    intv[[i]] <- data.table::as.ITime(seq.int(t1, t2, by))
  }

  do.call(c, intv)
}

#' Split Ashare intraday sessions to time intervals
#'
#' @param period size of interval in seconds
#'
#' @return a vector of ITime
#' @export
#'
itime_interval_ashare <- function(period)
{
  by <- as.integer(period)
  if (7200L %% by)
  {
    msg <- sprintf("Can't split trading hour by %d seconds", by)
    stop(msg, call. = FALSE)
  }

  #09:30, 11:30, shift 3.5 hr / 12600 sec for afternoon section
  t1 <- 34200L
  t2 <- 41400L
  intv <- seq.int(t1, t2, by)
  data.table::as.ITime(c(intv, intv + 12600L))
}

#' Group time to corresponding intervals, left open
#'
#' @param t ITime, time points to group
#' @param intv ITime, grouped intervals
#' @param grp_id whether to return group id or grouped time
#'
#' @return Integer or ITime depending on grp_id
#' @export
#'
itime_interval_group <- function(t, intv, grp_id = TRUE)
{
  t_grpid <- findInterval(t, intv, left.open = TRUE)
  if (grp_id)
  {
    t_grpid
  } else {
    t_grpid <- t_grpid + 1L
    if (any(t_grpid > length(intv)))
    {
      # set to last t available
      intv <- c(intv, data.table::last(t))
    }
    intv[t_grpid]
  }
}

#' Convert POSIXct time column to IDate and ITime
#'
#' @param dt data.table
#' @param time time column name
#' @param date date column name
#'
#' @return dt, columns converted by ref
#' @export
#'
itime_conv_idatetime <- function(dt, time = "Time", date = "Date")
{
  data.table::setDT(dt)

  if (is.null(dt[[date]]))
  {
    # force tz to UTC for faster as.IDate and as.ITime
    data.table::set(dt, j = time, value = lubridate::force_tz(dt[[time]], tzone = "UTC"))
    data.table::set(dt, j = date, value = data.table::as.IDate(dt[[time]]))
    data.table::set(dt, j = time, value = data.table::as.ITime(dt[[time]]))
  } else {
    data.table::set(dt, j = date, value = data.table::as.IDate(dt[[date]]))
    data.table::set(dt, j = time, value = data.table::as.ITime(dt[[time]]))
  }

  dt
}

#' Offline aggregation of OHLCV data
#'
#' @param dt data.table, input data
#' @param acc volume and turnover are accumulated in dt
#' @param code code column name
#' @param date date column name
#' @param time time column name
#' @param price price column name
#' @param volume volume column name
#' @param turnover turnover column name
#'
#' @return data.table, side effect: dt is setkey on code, date and time
#' @export
#'
aggr_ohlc <- function(dt, acc = NA,
                      code = "Code",
                      date = "Date",
                      time = "Time",
                      price = "Price",
                      volume = "Volume",
                      turnover = "Turnover")
{
  data.table::setDT(dt)

  # gForce doesn't work with get(COLUMN_NAME)
  data.table::setnames(dt, old = c(code, date, time, price, volume, turnover),
                           new = c("code", "date", "time", "price", "volume", "turnover"),
                       skip_absent = TRUE)
  on.exit({
    data.table::setnames(dt, old = c("code", "date", "time", "price", "volume", "turnover"),
                             new = c(code, date, time, price, volume, turnover),
                         skip_absent = TRUE)
  })

  if (is.null(dt$date))
  {
    grp_all <- c("code", "time")
    grp_date <- c("code")
  } else {
    grp_all <- c("code", "date", "time")
    grp_date <- c("code", "date")
  }
  data.table::setkeyv(dt, grp_all)

  if (is.na(acc))
  {
    test_code <- unique(dt$code)
    test_n    <- length(test_code) %/% 100L + 1L
    test_code <- sample(test_code, test_n)
    test_dt   <- dt[code %chin% test_code, .(unsorted = is.unsorted(volume)), by = grp_date]
    acc <- !any(test_dt$unsorted)
  }

  if (acc)
  {
    dt[, `:=`(d_volume   = volume   - data.table::shift(volume,   n = 1L, fill = 0),
              d_turnover = turnover - data.table::shift(turnover, n = 1L, fill = 0)), by = grp_date]
    ohlc <- dt[, list(open  = first(price),
                      high  = max(price),
                      low   = min(price),
                      close = last(price),
                      vol   = sum(d_volume),
                      tnvr  = sum(d_turnover)), by = grp_all][volume != 0]
    dt[, `:=`(d_volume = NULL, d_turnover = NULL)]
  } else {
    ohlc <- dt[, list(open  = first(price),
                      high  = max(price),
                      low   = min(price),
                      close = last(price),
                      vol   = sum(volume),
                      tnvr  = sum(turnover)), by = grp_all]
  }

  data.table::setkeyv(ohlc, grp_all)
  # integer overflow
  ohlc[, `:=`(vol = as.numeric(vol), tnvr = as.numeric(tnvr))]
  ohlc[, vwap := cumsum(tnvr) / cumsum(vol), by = grp_date]

  ohlc
}

#' Convenient OHLC aggr for in-house used data
#'
#' @param dt data.table
#'
#' @return data.table
#' @export
#'
aggr_ohlc_tick5 <- function(dt)
{
  # faster as.IDate/as.ITime
  dt[, tfutc := lubridate::force_tz(Time, tzone = "UTC")]
  dt[, date := data.table::as.IDate(tfutc)]
  dt[, time := data.table::as.ITime(tfutc)]
  aggr_ohlc(dt, FALSE,
            code = "Code",
            date = "date",
            time = "time",
            price = "Price",
            volume = "Vol",
            turnover = "Tnvr")
}

#' @rdname aggr_ohlc_tick5
#' @export
#'
aggr_ohlc_book <- function(dt)
{
  # faster as.IDate/as.ITime
  dt[, tfutc := lubridate::force_tz(Time, tzone = "UTC")]
  dt[, date := data.table::as.IDate(Date)]
  dt[, time := data.table::as.ITime(tfutc)]
  aggr_ohlc(dt, FALSE,
            code = "Code",
            date = "date",
            time = "time",
            price = "Price",
            volume = "Volume",
            turnover = "Turnover")
}

#' Resample OHLCV data
#'
#' @param dt data.table, input data
#' @param period resample time period
#' @param intv resample time interval
#' @param align_time align time to intv
#' @param code code column name
#' @param date date column name
#' @param time time column name
#' @param open open column name
#' @param high high column name
#' @param low low column name
#' @param close close column name
#' @param volume volume column name
#' @param turnover turnover column name
#' @param vwap vwap column name
#'
#' @return data.table, side effect: time column of dt is converted to ITime
#' @export
#'
aggr_ohlc_resample <- function(dt, period,
                               intv = itime_interval_ashare(period),
                               align_time = TRUE,
                               code     = "code",
                               date     = "date",
                               time     = "time",
                               open     = "open",
                               high     = "high",
                               low      = "low",
                               close    = "close",
                               volume   = "vol",
                               turnover = "tnvr",
                               vwap     = "vwap")
{
  data.table::setDT(dt)

  data.table::setnames(dt, old = c(code, date, time, open, high, low, close, volume, turnover, vwap),
                           new = c("code", "date", "time", "open", "high", "low", "close", "volume", "turnover", "vwap"),
                       skip_absent = TRUE)
  on.exit({
    data.table::setnames(dt, old = c("code", "date", "time", "open", "high", "low", "close", "volume", "turnover", "vwap"),
                             new = c(code, date, time, open, high, low, close, volume, turnover, vwap),
                         skip_absent = TRUE)
  })

  if (is.null(dt$date))
  {
    grp_all <- c("code", "time")
    grp_date <- c("code")
  } else {
    grp_all <- c("code", "date", "time")
    grp_date <- c("code", "date")
  }
  data.table::setkeyv(dt, grp_all)

  if (!is(dt$time, "ITime"))
  {
    dt[, time := data.table::as.ITime(time)]
  }
  if (align_time)
  {
    dt[, t_grps := itime_interval_group(t = time, intv = intv, grp_id = FALSE), by = grp_date]
    x <- dt[, list(open  = first(open),
                   high  = max(high),
                   low   = min(low),
                   close = last(close),
                   vol   = sum(volume),
                   tnvr  = sum(turnover),
                   vwap  = last(vwap)), by = c(grp_date, "t_grps")]
    data.table::setnames(x, old = "t_grps", "time")
  } else {
    dt[, t_grps := itime_interval_group(t = time, intv = intv, grp_id = TRUE), by = grp_date]
    x <- dt[, list(time  = last(time),
                   open  = first(open),
                   high  = max(high),
                   low   = min(low),
                   close = last(close),
                   vol   = sum(volume),
                   tnvr  = sum(turnover),
                   vwap  = last(vwap)), by = c(grp_date, "t_grps")]
    x[, t_grps := NULL]
  }
  dt[, t_grps := NULL]

  x
}
